#include <string.h>
#include <pthread.h>
#include <stdio.h>
#include <time.h>

#include <sys/syscall.h>

struct SharedData
{
    /* incremented by producers */
    int i;

    /* when i reaches this value, i is reset to 0 and a consumer is
     * awakened */
    int threshold;

    pthread_mutex_t lock;
    pthread_cond_t threshold_reached;
};

static pid_t thread_id()
{
    (pid_t)syscall(SYS_gettid);
}

static void* producer(void* v_shared)
{
    struct SharedData *shared = v_shared;

    for (;;) {
        struct timespec time;
        time.tv_sec = 0;
        time.tv_nsec = 1000*1000; /* 1 ms */
        nanosleep(&time, NULL);
        
        pthread_mutex_lock(&shared->lock);

        shared->i++;
        if (shared->i == shared->threshold) {
            fprintf(stderr, "Producer %d: threshold reached\n", thread_id());
            pthread_cond_signal(&shared->threshold_reached);
        }

        pthread_mutex_unlock(&shared->lock);
    }
}

static void* consumer(void* v_shared)
{
    struct SharedData *shared = v_shared;

    for (;;) {
        pthread_mutex_lock(&shared->lock);

        /* (re-)evaluate the predicate. there might have been a
         * spurious wakeup, or the condition variable might have been
         * broadcasted. */
        while (shared->i < shared->threshold)
            pthread_cond_wait(&shared->threshold_reached, &shared->lock);

        /* here we are sure that the threshold has been reached. reset
         * i to start the next round. */
        fprintf(stderr, "Consumer %d: threshold reached\n", thread_id());
        shared->i = 0;

        pthread_mutex_unlock(&shared->lock);
    }
}

int main()
{
    struct SharedData shared;
    int error, i;

    shared.i = 0;
    shared.threshold = 1000;
    pthread_mutex_init(&shared.lock, NULL);
    pthread_cond_init(&shared.threshold_reached, NULL);

    pthread_t tproducer, tconsumer;

    error = pthread_create(&tproducer, NULL, producer, &shared);
    if (error != 0) {
        fprintf(stderr, "pthread_create producer: %s\n", strerror(error));
        return 1;
    }

    error = pthread_create(&tconsumer, NULL, consumer, &shared);
    if (error != 0) {
        fprintf(stderr, "pthread_create consumer: %s\n", strerror(error));
        return 1;
    }

    
    error = pthread_join(tproducer, NULL);
    if (error != 0) {
        fprintf(stderr, "pthread_join producer: %s\n", strerror(error));
        return 1;
    }

    error = pthread_join(tconsumer, NULL);
    if (error != 0) {
        fprintf(stderr, "pthread_join consumer: %s\n", strerror(error));
        return 1;
    }

    return 0;
}
